Use select2 for type selection; switch to showing JSON for event information

Andrew Cantino 11 years ago
parent
commit
86b5b1f203

+ 4 - 4
app/assets/javascripts/application.js.coffee.erb

@@ -49,7 +49,7 @@ showEventDescriptions = ->
49 49
 
50 50
 $(document).ready ->
51 51
   setupJsonEditor()
52
-  $(".multi-select").select2(width: 'resolve')
52
+  $(".select2").select2(width: 'resolve')
53 53
 
54 54
   if $(".top-flash").length
55 55
     setTimeout((-> $(".top-flash").slideUp(-> $(".top-flash").remove())), 5000)
@@ -58,8 +58,8 @@ $(document).ready ->
58 58
 
59 59
   $("#agent_type").on "change", ->
60 60
     if window.jsonEditor?
61
-      $(@).closest(".control-group").find(".spinner").fadeIn();
62
-      $("#agent_source_ids ").select2("val", {});
61
+      $(".spinner").fadeIn();
62
+      $("#agent_source_ids").select2("val", {});
63 63
       $(".event-descriptions").html("").hide()
64 64
       $.getJSON "/agents/type_details", { type: $(@).val() }, (json) =>
65 65
         if json.can_be_scheduled
@@ -77,7 +77,7 @@ $(document).ready ->
77 77
         window.jsonEditor.json = json.options
78 78
         window.jsonEditor.rebuild()
79 79
 
80
-        $(@).closest(".control-group").find(".spinner").stop(true, true).fadeOut();
80
+        $(".spinner").stop(true, true).fadeOut();
81 81
 
82 82
   $("#agent_type").change() if $("#agent_type").length
83 83
 

+ 3 - 8
app/assets/stylesheets/application.css.scss.erb

@@ -46,7 +46,7 @@ table.events {
46 46
   }
47 47
 }
48 48
 
49
-.multi-select {
49
+.select2 {
50 50
   float: none !important;
51 51
   margin-left: 0 !important;
52 52
 }
@@ -65,13 +65,8 @@ img.odin {
65 65
   display: none;
66 66
 }
67 67
 
68
-.type-select {
69
-  width: 275px;
70
-
71
-  img.spinner {
72
-    display: none;
73
-    float: right;
74
-  }
68
+img.spinner {
69
+  display: none;
75 70
 }
76 71
 
77 72
 .hidden {

+ 8 - 4
app/models/agents/adioso_agent.rb

@@ -14,13 +14,17 @@ module Agents
14 14
     event_description <<-MD
15 15
       If flights are present then events look like:
16 16
 
17
-  		    { "cost" : 75.23,
18
-            "date" : "June 25, 2013",
19
-  			    "route" : "New York to Chicago" }
17
+          {
18
+            "cost": 75.23,
19
+            "date": "June 25, 2013",
20
+  			    "route": "New York to Chicago"
21
+          }
20 22
 
21 23
       otherwise
22 24
     
23
-          { "nonetodest" : "No flights found to the specified destination" }
25
+          {
26
+            "nonetodest": "No flights found to the specified destination"
27
+          }
24 28
     MD
25 29
 
26 30
     def default_options

+ 1 - 3
app/models/agents/event_formatting_agent.rb

@@ -44,9 +44,7 @@ module Agents
44 44
           }
45 45
     MD
46 46
 
47
-    event_description <<-MD
48
-      User defined
49
-    MD
47
+    event_description "User defined"
50 48
 
51 49
     def validate_options
52 50
       errors.add(:base, "instructions, mode, skip_agent, and skip_created_at all need to be present.") unless options[:instructions].present? and options[:mode].present? and options[:skip_agent].present? and options[:skip_created_at].present?

+ 10 - 5
app/models/agents/peak_detector_agent.rb

@@ -17,9 +17,14 @@ module Agents
17 17
     MD
18 18
 
19 19
     event_description <<-MD
20
-      Events look like this:
21
-
22
-          { :message => "Your message", :peak => 6, :peak_time => 3456789242, :grouped_by => "something" }
20
+      Events look like:
21
+
22
+          {
23
+            "message": "Your message",
24
+            "peak": 6,
25
+            "peak_time": 3456789242,
26
+            "grouped_by": "something"
27
+          }
23 28
     MD
24 29
 
25 30
     def validate_options
@@ -69,13 +74,13 @@ module Agents
69 74
         if newest_value < second_newest_value && second_newest_value > average_value + std_multiple * standard_deviation
70 75
           memory[:peaks][group] << second_newest_time
71 76
           memory[:peaks][group].reject! { |p| p <= second_newest_time - window_duration }
72
-          create_event :payload => { :message => options[:message], :peak => second_newest_value, :peak_time => second_newest_time, :grouped_by => group.to_s }
77
+          create_event :payload => {:message => options[:message], :peak => second_newest_value, :peak_time => second_newest_time, :grouped_by => group.to_s}
73 78
         end
74 79
       end
75 80
     end
76 81
 
77 82
     def stats_for(group, options = {})
78
-      data = memory[:data][group].map {|d| d.first.to_f }
83
+      data = memory[:data][group].map { |d| d.first.to_f }
79 84
       data = data[0...(memory[:data][group].length - (options[:skip_last] || 0))]
80 85
       length = data.length.to_f
81 86
       mean = 0

+ 31 - 33
app/models/agents/post_agent.rb

@@ -1,43 +1,41 @@
1 1
 module Agents
2
-    class PostAgent < Agent
3
-        cannot_be_scheduled!
2
+  class PostAgent < Agent
3
+    cannot_be_scheduled!
4 4
 
5
-        description <<-MD
6
-            Post Agent receives events from other agents and send those events as the contents of a post request to a specified url. `post_url` field must specify where you would like to receive post requests and do not forget to include URI scheme(`http` or `https`)
7
-        MD
5
+    description <<-MD
6
+       Post Agent receives events from other agents and send those events as the contents of a post request to a specified url. `post_url` field must specify where you would like to receive post requests and do not forget to include URI scheme (`http` or `https`)
7
+    MD
8 8
 
9
-        event_description <<-MD
10
-            Does not produce any event.
11
-        MD
9
+    event_description "Does not produce events."
12 10
 
13
-        def default_options
14
-            {
15
-                :post_url => "http://www.example.com",
16
-                :expected_receive_period_in_days => 1
17
-            }
18
-        end
11
+    def default_options
12
+      {
13
+        :post_url => "http://www.example.com",
14
+        :expected_receive_period_in_days => 1
15
+      }
16
+    end
19 17
 
20
-        def working?
21
-            last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
22
-        end
18
+    def working?
19
+      last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
20
+    end
23 21
 
24
-        def validate_options
25
-            unless options[:post_url].present? && options[:expected_receive_period_in_days].present? 
26
-                errors.add(:base, "post_url and expected_receive_period_in_days are required fields")
27
-            end
28
-        end
22
+    def validate_options
23
+      unless options[:post_url].present? && options[:expected_receive_period_in_days].present?
24
+        errors.add(:base, "post_url and expected_receive_period_in_days are required fields")
25
+      end
26
+    end
29 27
 
30
-        def post_event(uri,event)
31
-            req = Net::HTTP::Post.new(uri.request_uri)
32
-            req.form_data = event
33
-            Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == "https") { |http| http.request(req) }
34
-        end
28
+    def post_event(uri, event)
29
+      req = Net::HTTP::Post.new(uri.request_uri)
30
+      req.form_data = event
31
+      Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == "https") { |http| http.request(req) }
32
+    end
35 33
 
36
-        def receive(incoming_events)
37
-            incoming_events.each do |event|
38
-                uri = URI options[:post_url]
39
-                post_event uri, event.payload
40
-            end
41
-        end
34
+    def receive(incoming_events)
35
+      incoming_events.each do |event|
36
+        uri = URI options[:post_url]
37
+        post_event uri, event.payload
38
+      end
42 39
     end
40
+  end
43 41
 end

+ 66 - 65
app/models/agents/sentiment_agent.rb

@@ -1,84 +1,85 @@
1 1
 require 'csv'
2 2
 
3 3
 module Agents
4
-    class SentimentAgent < Agent
5
-        class_attribute :anew
4
+  class SentimentAgent < Agent
5
+    class_attribute :anew
6 6
 
7
-        cannot_be_scheduled!
7
+    cannot_be_scheduled!
8 8
 
9
-        description <<-MD
10
-            The SentimentAgent generates `good-bad` (psychological valence or happiness index), `active-passive` (arousal),
11
-            and  `strong-weak` (dominance) score. It will output a value between 1 and 9. It will only work on English content.
9
+    description <<-MD
10
+      The SentimentAgent generates `good-bad` (psychological valence or happiness index), `active-passive` (arousal),
11
+      and  `strong-weak` (dominance) score. It will output a value between 1 and 9. It will only work on English content.
12 12
 
13
-            Make sure the content this agent is analyzing have sufficient length to get respectable results.
13
+      Make sure the content this agent is analyzing have sufficient length to get respectable results.
14 14
 
15
-            Provide a JSONPath in `content` field where content is residing and set `expected_receive_period_in_days` to the maximum number of days you would allow to be passed between events being received by this agent.
16
-        MD
15
+      Provide a JSONPath in `content` field where content is residing and set `expected_receive_period_in_days` to the maximum number of days you would allow to be passed between events being received by this agent.
16
+    MD
17 17
 
18
-        event_description <<-MD
19
-            Events look like:
20
-            {
21
-                :content   => "The quick brown fox jumps over the lazy dog.",
22
-                :valence   => 6.196666666666666,
23
-                :arousal   => 4.993333333333333,
24
-                :dominance => 5.63
25
-            }
26
-        MD
18
+    event_description <<-MD
19
+      Events look like:
27 20
 
28
-        def default_options
29
-            {
30
-                :content => "$.message.text[*]",
31
-                :expected_receive_period_in_days => 1
32
-            }
33
-        end
21
+          {
22
+            "content": "The quick brown fox jumps over the lazy dog.",
23
+            "valence": 6.196666666666666,
24
+            "arousal": 4.993333333333333,
25
+            "dominance": 5.63
26
+          }
27
+    MD
34 28
 
35
-        def working?
36
-            last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
37
-        end
29
+    def default_options
30
+      {
31
+        :content => "$.message.text[*]",
32
+        :expected_receive_period_in_days => 1
33
+      }
34
+    end
38 35
 
39
-        def receive(incoming_events)
40
-            anew = self.class.sentiment_hash
41
-            incoming_events.each do |event|
42
-                Utils.values_at(event.payload, options[:content]).each do |content|
43
-                    sent_values = sentiment_values anew, content
44
-                    create_event :payload => {:content => content, 
45
-                                              :valence => sent_values[0], 
46
-                                              :arousal => sent_values[1], 
47
-                                              :dominance => sent_values[2],
48
-                                              :original_event => event.payload}
49
-                end
50
-            end
51
-        end
36
+    def working?
37
+      last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
38
+    end
52 39
 
53
-        def validate_options
54
-            errors.add(:base, "content and expected_receive_period_in_days must be present") unless options[:content].present? && options[:expected_receive_period_in_days].present?
40
+    def receive(incoming_events)
41
+      anew = self.class.sentiment_hash
42
+      incoming_events.each do |event|
43
+        Utils.values_at(event.payload, options[:content]).each do |content|
44
+          sent_values = sentiment_values anew, content
45
+          create_event :payload => { :content => content,
46
+                                     :valence => sent_values[0],
47
+                                     :arousal => sent_values[1],
48
+                                     :dominance => sent_values[2],
49
+                                     :original_event => event.payload }
55 50
         end
51
+      end
52
+    end
53
+
54
+    def validate_options
55
+      errors.add(:base, "content and expected_receive_period_in_days must be present") unless options[:content].present? && options[:expected_receive_period_in_days].present?
56
+    end
56 57
 
57
-        def self.sentiment_hash
58
-            unless self.anew
59
-                self.anew = {}
60
-                CSV.foreach Rails.root.join('data/anew.csv') do |row|
61
-                    self.anew[row[0]] = row.values_at(2,4,6).map {|val| val.to_f}
62
-                end
63
-            end
64
-            self.anew  
58
+    def self.sentiment_hash
59
+      unless self.anew
60
+        self.anew = {}
61
+        CSV.foreach Rails.root.join('data/anew.csv') do |row|
62
+          self.anew[row[0]] = row.values_at(2, 4, 6).map { |val| val.to_f }
65 63
         end
64
+      end
65
+      self.anew
66
+    end
66 67
 
67
-        def sentiment_values(anew,text)
68
-            valence, arousal, dominance, freq = [0] * 4
69
-            text.downcase.strip.gsub(/[^a-z ]/,"").split.each do |word|
70
-                if anew.has_key? word
71
-                    valence   += anew[word][0]
72
-                    arousal   += anew[word][1]
73
-                    dominance += anew[word][2]
74
-                    freq      += 1
75
-                end
76
-            end
77
-            if valence != 0
78
-                [valence/freq, arousal/freq, dominance/freq]
79
-            else
80
-                ["Insufficient data for meaningful answer"] * 3
81
-            end
68
+    def sentiment_values(anew, text)
69
+      valence, arousal, dominance, freq = [0] * 4
70
+      text.downcase.strip.gsub(/[^a-z ]/, "").split.each do |word|
71
+        if anew.has_key? word
72
+          valence += anew[word][0]
73
+          arousal += anew[word][1]
74
+          dominance += anew[word][2]
75
+          freq += 1
82 76
         end
77
+      end
78
+      if valence != 0
79
+        [valence/freq, arousal/freq, dominance/freq]
80
+      else
81
+        ["Insufficient data for meaningful answer"] * 3
82
+      end
83 83
     end
84
+  end
84 85
 end

+ 63 - 65
app/models/agents/translation_agent.rb

@@ -1,80 +1,78 @@
1 1
 module Agents
2
-    class TranslationAgent < Agent
2
+  class TranslationAgent < Agent
3 3
 
4
-        cannot_be_scheduled!
4
+    cannot_be_scheduled!
5 5
 
6
-        description <<-MD
7
-            You can use Translation Agent to translate text between natural languages. 
8
-            Services are provided using Microsoft Translator. You can [sign up](https://datamarket.azure.com/dataset/bing/microsofttranslator) and [register your application](https://datamarket.azure.com/developer/applications/register) to get `client_id` and `client_secret` which are required to use this agent.
9
-            `to` must be filled with a [translator language code](http://msdn.microsoft.com/en-us/library/hh456380.aspx).
6
+    description <<-MD
7
+      You can use Translation Agent to translate text between natural languages.
8
+      Services are provided using Microsoft Translator. You can [sign up](https://datamarket.azure.com/dataset/bing/microsofttranslator) and [register your application](https://datamarket.azure.com/developer/applications/register) to get `client_id` and `client_secret` which are required to use this agent.
9
+      `to` must be filled with a [translator language code](http://msdn.microsoft.com/en-us/library/hh456380.aspx).
10 10
 
11
-            Specify what you would like to translate in `content` field, by specifying key and JSONPath of content to be translated.
11
+      Specify what you would like to translate in `content` field, by specifying key and JSONPath of content to be translated.
12 12
 
13
-            `expected_receive_period_in_days` is the maximum number of days you would allow to pass between events.
14
-        MD
13
+      `expected_receive_period_in_days` is the maximum number of days you would allow to pass between events.
14
+    MD
15 15
 
16
-        event_description <<-MD
17
-            User defined
18
-        MD
16
+    event_description "User defined"
19 17
 
20
-        def default_options
21
-            {
22
-                :client_id     => "xxxxxx",
23
-                :client_secret => "xxxxxx" ,
24
-                :to            => "fi",
25
-                :expected_receive_period_in_days => 1,
26
-                :content       => {
27
-                    :text => "$.message.text",
28
-                    :content => "$.xyz"
29
-                }
30
-            }
31
-        end
18
+    def default_options
19
+      {
20
+        :client_id => "xxxxxx",
21
+        :client_secret => "xxxxxx",
22
+        :to => "fi",
23
+        :expected_receive_period_in_days => 1,
24
+        :content => {
25
+          :text => "$.message.text",
26
+          :content => "$.xyz"
27
+        }
28
+      }
29
+    end
32 30
 
33
-        def working?
34
-            last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
35
-        end
31
+    def working?
32
+      last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
33
+    end
36 34
 
37
-        def translate(text,to,access_token)
38
-            translate_uri = URI 'http://api.microsofttranslator.com/v2/Ajax.svc/Translate'
39
-            params = {
40
-                :text => text,
41
-                :to   => to
42
-            }
43
-            translate_uri.query = URI.encode_www_form params
44
-            request = Net::HTTP::Get.new translate_uri.request_uri
45
-            request['Authorization'] = "Bearer" + " " + access_token
46
-            http = Net::HTTP.new translate_uri.hostname, translate_uri.port 
47
-            response = http.request request 
48
-            YAML.load response.body
49
-        end
35
+    def translate(text, to, access_token)
36
+      translate_uri = URI 'http://api.microsofttranslator.com/v2/Ajax.svc/Translate'
37
+      params = {
38
+          :text => text,
39
+          :to => to
40
+      }
41
+      translate_uri.query = URI.encode_www_form params
42
+      request = Net::HTTP::Get.new translate_uri.request_uri
43
+      request['Authorization'] = "Bearer" + " " + access_token
44
+      http = Net::HTTP.new translate_uri.hostname, translate_uri.port
45
+      response = http.request request
46
+      YAML.load response.body
47
+    end
50 48
 
51
-        def validate_options
52
-            unless options[:client_id].present? && options[:client_secret].present? && options[:to].present? && options[:content].present? && options[:expected_receive_period_in_days].present?
53
-                errors.add :base, "client_id,client_secret,to,expected_receive_period_in_days and content are all required"
54
-            end
55
-        end
49
+    def validate_options
50
+      unless options[:client_id].present? && options[:client_secret].present? && options[:to].present? && options[:content].present? && options[:expected_receive_period_in_days].present?
51
+        errors.add :base, "client_id,client_secret,to,expected_receive_period_in_days and content are all required"
52
+      end
53
+    end
56 54
 
57
-        def postform(uri,params)
58
-            req = Net::HTTP::Post.new(uri.request_uri)
59
-            req.form_data = params
60
-            Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) { |http| http.request(req) }
61
-        end
55
+    def postform(uri, params)
56
+      req = Net::HTTP::Post.new(uri.request_uri)
57
+      req.form_data = params
58
+      Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) { |http| http.request(req) }
59
+    end
62 60
 
63
-        def receive(incoming_events)
64
-            auth_uri = URI "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"
65
-            response = postform auth_uri, :client_id => options[:client_id],
66
-                                          :client_secret => options[:client_secret],
67
-                                          :scope => "http://api.microsofttranslator.com",
68
-                                          :grant_type =>"client_credentials"
69
-            access_token = JSON.parse(response.body)["access_token"]
70
-            incoming_events.each do |event|
71
-                translated_event = {}
72
-                options[:content].each_pair do |key,value|
73
-                    to_be_translated = Utils.values_at event.payload, value 
74
-                    translated_event[key] = translate to_be_translated.first, options[:to], access_token
75
-                end
76
-                create_event :payload => translated_event
77
-            end
61
+    def receive(incoming_events)
62
+      auth_uri = URI "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"
63
+      response = postform auth_uri, :client_id => options[:client_id],
64
+                                    :client_secret => options[:client_secret],
65
+                                    :scope => "http://api.microsofttranslator.com",
66
+                                    :grant_type => "client_credentials"
67
+      access_token = JSON.parse(response.body)["access_token"]
68
+      incoming_events.each do |event|
69
+        translated_event = {}
70
+        options[:content].each_pair do |key, value|
71
+          to_be_translated = Utils.values_at event.payload, value
72
+          translated_event[key] = translate to_be_translated.first, options[:to], access_token
78 73
         end
74
+        create_event :payload => translated_event
75
+      end
79 76
     end
77
+  end
80 78
 end

+ 1 - 1
app/models/agents/trigger_agent.rb

@@ -19,7 +19,7 @@ module Agents
19 19
     event_description <<-MD
20 20
       Events look like this:
21 21
 
22
-          { :message => "Your message" }
22
+          { "message": "Your message" }
23 23
     MD
24 24
 
25 25
     def validate_options

+ 22 - 19
app/models/agents/twitter_stream_agent.rb

@@ -19,30 +19,33 @@ module Agents
19 19
       When in `counts` mode, TwitterStreamAgent events look like:
20 20
 
21 21
           {
22
-            :filter => "hello world",
23
-            :count => 25,
24
-            :time => 3456785456
22
+            "filter": "hello world",
23
+            "count": 25,
24
+            "time": 3456785456
25 25
           }
26 26
 
27 27
       When in `events` mode, TwitterStreamAgent events look like:
28 28
 
29
-          { :filter=>"selectorgadget",
29
+          {
30
+            "filter": "selectorgadget",
30 31
              ... every Tweet field, including ...
31
-           :text=> "something",
32
-           :user=>
33
-            { :name=>"Mr. Someone",
34
-              :screen_name=>"Someone",
35
-              :location=>"Vancouver BC Canada",
36
-              :description=> "...",
37
-              :followers_count=>486,
38
-              :friends_count=>1983,
39
-              :created_at=>"Mon Aug 29 23:38:14 +0000 2011",
40
-              :time_zone=>"Pacific Time (US & Canada)",
41
-              :statuses_count=>3807,
42
-              :lang=>"en" },
43
-           :retweet_count=>0,
44
-           :entities=> ...
45
-           :lang=>"en" }
32
+            "text": "something",
33
+            "user": {
34
+              "name": "Mr. Someone",
35
+              "screen_name": "Someone",
36
+              "location": "Vancouver BC Canada",
37
+              "description":  "...",
38
+              "followers_count": 486,
39
+              "friends_count": 1983,
40
+              "created_at": "Mon Aug 29 23:38:14 +0000 2011",
41
+              "time_zone": "Pacific Time (US & Canada)",
42
+              "statuses_count": 3807,
43
+              "lang": "en"
44
+            },
45
+            "retweet_count": 0,
46
+            "entities": ...
47
+            "lang": "en"
48
+          }
46 49
     MD
47 50
 
48 51
     default_schedule "11pm"

+ 19 - 89
app/models/agents/twitter_user_agent.rb

@@ -15,95 +15,25 @@ module Agents
15 15
     event_description <<-MD
16 16
       Events are the raw JSON provided by the Twitter API. Should look something like:
17 17
 
18
-        {
19
-         :created_at=>"Thu Apr 04 13:27:48 +0000 2013",
20
-         :id=>319803490421596160,
21
-         :id_str=>"319803490421596160",
22
-         :text=>
23
-          "In which @jeresig goes to an art gallery and is \"the JavaScript programmer\". http://t.co/gt3PT1d3G1",
24
-         :source=>
25
-          "<a href=\"http://itunes.apple.com/us/app/twitter/id409789998?mt=12\" rel=\"nofollow\">Twitter for Mac</a>",
26
-         :truncated=>false,
27
-         :in_reply_to_status_id=>nil,
28
-         :in_reply_to_status_id_str=>nil,
29
-         :in_reply_to_user_id=>nil,
30
-         :in_reply_to_user_id_str=>nil,
31
-         :in_reply_to_screen_name=>nil,
32
-         :user=>
33
-          {:id=>2341001,
34
-           :id_str=>"2341001",
35
-           :name=>"Albert Sun",
36
-           :screen_name=>"albertsun",
37
-           :location=>"New York, NY",
38
-           :description=>
39
-            "News apps developer at NYT, formerly WSJ. graduated Penn 2010, geek, journalist, data-viz, nlp, gis, digital economics =)",
40
-           :url=>"http://albertsun.info",
41
-           :entities=>
42
-            {:url=>
43
-              {:urls=>
44
-                [{:url=>"http://albertsun.info",
45
-                  :expanded_url=>nil,
46
-                  :indices=>[0, 21]}]},
47
-             :description=>{:urls=>[]}},
48
-           :protected=>false,
49
-           :followers_count=>1857,
50
-           :friends_count=>798,
51
-           :listed_count=>115,
52
-           :created_at=>"Mon Mar 26 19:22:05 +0000 2007",
53
-           :favourites_count=>9,
54
-           :utc_offset=>-18000,
55
-           :time_zone=>"Eastern Time (US & Canada)",
56
-           :geo_enabled=>false,
57
-           :verified=>false,
58
-           :statuses_count=>2572,
59
-           :lang=>"en",
60
-           :contributors_enabled=>false,
61
-           :is_translator=>false,
62
-           :profile_background_color=>"1B2A2B",
63
-           :profile_background_image_url=>
64
-            "http://a0.twimg.com/profile_background_images/2802438/twitterbg.jpg",
65
-           :profile_background_image_url_https=>
66
-            "https://si0.twimg.com/profile_background_images/2802438/twitterbg.jpg",
67
-           :profile_background_tile=>false,
68
-           :profile_image_url=>
69
-            "http://a0.twimg.com/profile_images/110500205/profile-square_normal.jpg",
70
-           :profile_image_url_https=>
71
-            "https://si0.twimg.com/profile_images/110500205/profile-square_normal.jpg",
72
-           :profile_link_color=>"0000FF",
73
-           :profile_sidebar_border_color=>"87BC44",
74
-           :profile_sidebar_fill_color=>"E0FF92",
75
-           :profile_text_color=>"000000",
76
-           :profile_use_background_image=>true,
77
-           :default_profile=>false,
78
-           :default_profile_image=>false,
79
-           :following=>false,
80
-           :follow_request_sent=>false,
81
-           :notifications=>false},
82
-         :geo=>nil,
83
-         :coordinates=>nil,
84
-         :place=>nil,
85
-         :contributors=>nil,
86
-         :retweet_count=>0,
87
-         :favorite_count=>0,
88
-         :entities=>
89
-          {:hashtags=>[],
90
-           :urls=>
91
-            [{:url=>"http://t.co/gt3PT1d3G1",
92
-              :expanded_url=>
93
-               "http://www.nytimes.com/2013/04/04/fashion/art-and-techology-a-clash-of-cultures.html?pagewanted=all",
94
-              :display_url=>"nytimes.com/2013/04/04/fas",
95
-              :indices=>[77, 99]}],
96
-           :user_mentions=>
97
-            [{:screen_name=>"jeresig",
98
-              :name=>"John Resig",
99
-              :id=>752673,
100
-              :id_str=>"752673",
101
-              :indices=>[9, 17]}]},
102
-         :favorited=>false,
103
-         :retweeted=>false,
104
-         :possibly_sensitive=>false,
105
-         :lang=>"en"
106
-        }
18
+          {
19
+             ... every Tweet field, including ...
20
+            "text": "something",
21
+            "user": {
22
+              "name": "Mr. Someone",
23
+              "screen_name": "Someone",
24
+              "location": "Vancouver BC Canada",
25
+              "description":  "...",
26
+              "followers_count": 486,
27
+              "friends_count": 1983,
28
+              "created_at": "Mon Aug 29 23:38:14 +0000 2011",
29
+              "time_zone": "Pacific Time (US & Canada)",
30
+              "statuses_count": 3807,
31
+              "lang": "en"
32
+            },
33
+            "retweet_count": 0,
34
+            "entities": ...
35
+            "lang": "en"
36
+          }
107 37
     MD
108 38
 
109 39
     default_schedule "every_1h"

+ 9 - 9
app/models/agents/user_location_agent.rb

@@ -17,15 +17,15 @@ module Agents
17 17
       Assuming you're using the iOS application, events look like this:
18 18
 
19 19
           {
20
-            :latitude => "37.12345",
21
-            :longitude => "-122.12345",
22
-            :timestamp => "123456789.0",
23
-            :altitude => "22.0",
24
-            :horizontal_accuracy => "5.0",
25
-            :vertical_accuracy => "3.0",
26
-            :speed => "0.52595",
27
-            :course => "72.0703",
28
-            :device_token => "..."
20
+            "latitude": "37.12345",
21
+            "longitude": "-122.12345",
22
+            "timestamp": "123456789.0",
23
+            "altitude": "22.0",
24
+            "horizontal_accuracy": "5.0",
25
+            "vertical_accuracy": "3.0",
26
+            "speed": "0.52595",
27
+            "course": "72.0703",
28
+            "device_token": "..."
29 29
           }
30 30
     MD
31 31
 

+ 18 - 20
app/models/agents/weather_agent.rb

@@ -14,26 +14,24 @@ module Agents
14 14
       Events look like this:
15 15
 
16 16
           {
17
-            :zipcode => 12345,
18
-            :date => { :epoch=>"1357959600", :pretty=>"10:00 PM EST on January 11, 2013" },
19
-            :high => { :fahrenheit=>"64", :celsius=>"18" },
20
-            :low => { :fahrenheit=>"52", :celsius=>"11" },
21
-            :conditions => "Rain Showers",
22
-            :icon=>"rain",
23
-            :icon_url => "http://icons-ak.wxug.com/i/c/k/rain.gif",
24
-            :skyicon => "mostlycloudy",
25
-            :pop => 80,
26
-            :qpf_allday => { :in=>0.24, :mm=>6.1 },
27
-            :qpf_day => { :in=>0.13, :mm=>3.3 },
28
-            :qpf_night => { :in=>0.03, :mm=>0.8 },
29
-            :snow_allday => { :in=>0, :cm=>0 },
30
-            :snow_day => { :in=>0, :cm=>0 },
31
-            :snow_night => { :in=>0, :cm=>0 },
32
-            :maxwind => { :mph=>15, :kph=>24, :dir=>"SSE", :degrees=>160 },
33
-            :avewind => { :mph=>9, :kph=>14, :dir=>"SSW", :degrees=>194 },
34
-            :avehumidity => 85,
35
-            :maxhumidity => 93,
36
-            :minhumidity => 63
17
+            "zipcode": 12345,
18
+            "date": {
19
+              "epoch": "1357959600",
20
+              "pretty": "10:00 PM EST on January 11, 2013"
21
+            },
22
+            "high": {
23
+              "fahrenheit": "64",
24
+              "celsius": "18"
25
+            },
26
+            "low": {
27
+              "fahrenheit": "52",
28
+              "celsius": "11"
29
+            },
30
+            "conditions": "Rain Showers",
31
+            "icon": "rain",
32
+            "icon_url": "http://icons-ak.wxug.com/i/c/k/rain.gif",
33
+            "skyicon": "mostlycloudy",
34
+            ...
37 35
           }
38 36
     MD
39 37
 

+ 1 - 5
app/models/agents/website_agent.rb

@@ -36,11 +36,7 @@ module Agents
36 36
     MD
37 37
 
38 38
     event_description do
39
-      <<-MD
40
-      Events will have the fields you specified.  Your options look like:
41
-
42
-          #{PP.pp(options[:extract], "")}
43
-      MD
39
+      "Events will have the fields you specified.  Your options look like:\n\n    #{Utils.pretty_print options[:extract]}"
44 40
     end
45 41
 
46 42
     default_schedule "every_12h"

+ 45 - 45
app/models/agents/weibo_user_agent.rb

@@ -16,51 +16,51 @@ module Agents
16 16
     MD
17 17
 
18 18
     event_description <<-MD
19
-      Events are the raw JSON provided by the Twitter API. Should look something like:
20
-
21
-        {
22
-          "created_at": "Tue May 31 17:46:55 +0800 2011",
23
-          "id": 11488058246,
24
-          "text": "求关注。",
25
-          "source": "<a href=\"http://weibo.com\" rel=\"nofollow\">新浪微博</a>",
26
-          "favorited": false,
27
-          "truncated": false,
28
-          "in_reply_to_status_id": "",
29
-          "in_reply_to_user_id": "",
30
-          "in_reply_to_screen_name": "",
31
-          "geo": null,
32
-          "mid": "5612814510546515491",
33
-          "reposts_count": 8,
34
-          "comments_count": 9,
35
-          "annotations": [],
36
-          "user": {
37
-              "id": 1404376560,
38
-              "screen_name": "zaku",
39
-              "name": "zaku",
40
-              "province": "11",
41
-              "city": "5",
42
-              "location": "北京 朝阳区",
43
-              "description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。",
44
-              "url": "http://blog.sina.com.cn/zaku",
45
-              "profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1",
46
-              "domain": "zaku",
47
-              "gender": "m",
48
-              "followers_count": 1204,
49
-              "friends_count": 447,
50
-              "statuses_count": 2908,
51
-              "favourites_count": 0,
52
-              "created_at": "Fri Aug 28 00:00:00 +0800 2009",
53
-              "following": false,
54
-              "allow_all_act_msg": false,
55
-              "remark": "",
56
-              "geo_enabled": true,
57
-              "verified": false,
58
-              "allow_all_comment": true,
59
-              "avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1",
60
-              "verified_reason": "",
61
-              "follow_me": false,
62
-              "online_status": 0,
63
-              "bi_followers_count": 215
19
+      Events are the raw JSON provided by the Weibo API. Should look something like:
20
+
21
+          {
22
+            "created_at": "Tue May 31 17:46:55 +0800 2011",
23
+            "id": 11488058246,
24
+            "text": "求关注。",
25
+            "source": "<a href=\"http://weibo.com\" rel=\"nofollow\">新浪微博</a>",
26
+            "favorited": false,
27
+            "truncated": false,
28
+            "in_reply_to_status_id": "",
29
+            "in_reply_to_user_id": "",
30
+            "in_reply_to_screen_name": "",
31
+            "geo": null,
32
+            "mid": "5612814510546515491",
33
+            "reposts_count": 8,
34
+            "comments_count": 9,
35
+            "annotations": [],
36
+            "user": {
37
+                "id": 1404376560,
38
+                "screen_name": "zaku",
39
+                "name": "zaku",
40
+                "province": "11",
41
+                "city": "5",
42
+                "location": "北京 朝阳区",
43
+                "description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。",
44
+                "url": "http://blog.sina.com.cn/zaku",
45
+                "profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1",
46
+                "domain": "zaku",
47
+                "gender": "m",
48
+                "followers_count": 1204,
49
+                "friends_count": 447,
50
+                "statuses_count": 2908,
51
+                "favourites_count": 0,
52
+                "created_at": "Fri Aug 28 00:00:00 +0800 2009",
53
+                "following": false,
54
+                "allow_all_act_msg": false,
55
+                "remark": "",
56
+                "geo_enabled": true,
57
+                "verified": false,
58
+                "allow_all_comment": true,
59
+                "avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1",
60
+                "verified_reason": "",
61
+                "follow_me": false,
62
+                "online_status": 0,
63
+                "bi_followers_count": 215
64 64
           }
65 65
         }
66 66
     MD

+ 3 - 4
app/views/agents/_form.html.erb

@@ -22,10 +22,9 @@
22 22
 
23 23
   <% if @agent.new_record? %>
24 24
     <div class="control-group type-select">
25
-      <%= image_tag "spinner-arrows.gif", :class => "spinner" %>
26 25
       <%= f.label :type, :class => 'control-label' %>
27 26
       <div class="controls">
28
-        <%= f.select :type, options_for_select(Agent.types.map {|type| [type.to_s.gsub(/^.*::/, ''), type.to_s] }, @agent.type), :class => 'span4' %>
27
+        <%= f.select :type, options_for_select(Agent.types.map {|type| [type.to_s.gsub(/^.*::/, ''), type.to_s] }, @agent.type), {}, :class => 'span4 select2' %>
29 28
       </div>
30 29
     </div>
31 30
   <% end %>
@@ -40,7 +39,7 @@
40 39
   <div class="control-group">
41 40
     <%= f.label :schedule, :class => 'control-label' %>
42 41
     <div class="controls schedule-region" data-can-be-scheduled="<%= @agent.can_be_scheduled? %>">
43
-      <%= f.select :schedule, options_for_select(Agent::SCHEDULES.map {|s| [s.humanize.titleize, s] }, @agent.schedule), :class => 'span4' %>
42
+      <%= f.select :schedule, options_for_select(Agent::SCHEDULES.map {|s| [s.humanize.titleize, s] }, @agent.schedule), {}, :class => 'span4' %>
44 43
       <span class='cannot-be-scheduled text-info'>This type of Agent cannot be scheduled.</span>
45 44
     </div>
46 45
   </div>
@@ -51,7 +50,7 @@
51 50
       <%= f.select(:source_ids,
52 51
                    options_for_select((current_user.agents - [@agent]).map {|s| [s.name, s.id] },
53 52
                                       @agent.source_ids),
54
-                   {}, { :multiple => true, :size => 5, :class => 'span4 multi-select' }) %>
53
+                   {}, { :multiple => true, :size => 5, :class => 'span4 select2' }) %>
55 54
       <span class='cannot-receive-events text-info'>This type of Agent cannot receive events.</span>
56 55
     </div>
57 56
   </div>

+ 4 - 1
app/views/agents/edit.html.erb

@@ -2,7 +2,10 @@
2 2
   <div class='row'>
3 3
     <div class='span12'>
4 4
       <div class="page-header">
5
-        <h2>Editing your <%= @agent.short_type %></h2>
5
+        <h2>
6
+          Editing your <%= @agent.short_type %>
7
+          <%= image_tag "spinner-arrows.gif", :class => "spinner" %>
8
+        </h2>
6 9
       </div>
7 10
 
8 11
       <%= render 'form' %>

+ 4 - 1
app/views/agents/new.html.erb

@@ -2,7 +2,10 @@
2 2
   <div class='row'>
3 3
     <div class='span12'>
4 4
       <div class="page-header">
5
-        <h2>Create a new Agent</h2>
5
+        <h2>
6
+          Create a new Agent
7
+          <%= image_tag "spinner-arrows.gif", :class => "spinner" %>
8
+        </h2>
6 9
       </div>
7 10
 
8 11
       <%= render 'form' %>

+ 2 - 2
app/views/agents/show.html.erb

@@ -90,12 +90,12 @@
90 90
 
91 91
             <p>
92 92
               <b>Options:</b>
93
-              <pre><%= PP.pp(@agent.options, "") %></pre>
93
+              <pre><%= JSON.pretty_generate @agent.options %></pre>
94 94
             </p>
95 95
 
96 96
             <p>
97 97
               <b>Memory:</b>
98
-              <pre><%= PP.pp(@agent.memory, "") %></pre>
98
+              <pre><%= JSON.pretty_generate @agent.memory %></pre>
99 99
             </p>
100 100
           </div>
101 101
         </div>

+ 1 - 1
app/views/events/show.html.erb

@@ -7,7 +7,7 @@
7 7
 
8 8
       <p>
9 9
         <b>Payload:</b>
10
-        <pre><%= PP.pp(@event.payload, "") %></pre>
10
+        <pre><%= JSON.pretty_generate @event.payload %></pre>
11 11
       </p>
12 12
 
13 13
       <% if @event.lat && @event.lng %>

+ 16 - 2
lib/utils.rb

@@ -2,9 +2,23 @@ require 'jsonpath'
2 2
 require 'cgi'
3 3
 
4 4
 module Utils
5
-  # Unindents if the indentation is 2 or more characters.
6 5
   def self.unindent(s)
7
-    s.gsub(/^#{s.scan(/^\s+/).select {|i| i.length > 1 }.min_by{|l|l.length}}/, "")
6
+    s = s.gsub(/\t/, '  ').chomp
7
+    min = ((s.split("\n").find {|l| l !~ /^\s*$/ })[/^\s+/, 0] || "").length
8
+    if min > 0
9
+      s.gsub(/^#{" " * min}/, "")
10
+    else
11
+      s
12
+    end
13
+  end
14
+
15
+  def self.pretty_print(struct, indent = true)
16
+    output = JSON.pretty_generate(struct)
17
+    if indent
18
+      output.gsub(/\n/i, "\n    ").tap { |a| p a }
19
+    else
20
+      output
21
+    end
8 22
   end
9 23
 
10 24
   def self.recursively_symbolize_keys(object)

+ 26 - 0
spec/lib/utils_spec.rb

@@ -1,6 +1,32 @@
1 1
 require 'spec_helper'
2 2
 
3 3
 describe Utils do
4
+  describe "#unindent" do
5
+    it "unindents to the level of the greatest consistant indention" do
6
+      Utils.unindent(<<-MD).should == "Hello World"
7
+        Hello World
8
+      MD
9
+
10
+      Utils.unindent(<<-MD).should == "Hello World\nThis is\nnot indented"
11
+        Hello World
12
+        This is
13
+        not indented
14
+      MD
15
+
16
+      Utils.unindent(<<-MD).should == "Hello World\n  This is\n  indented\nthough"
17
+        Hello World
18
+          This is
19
+          indented
20
+        though
21
+      MD
22
+
23
+      Utils.unindent("Hello\n  I am indented").should == "Hello\n  I am indented"
24
+
25
+      a = "        Events will have the fields you specified.  Your options look like:\n\n            {\n      \"url\": {\n        \"css\": \"#comic img\",\n        \"attr\": \"src\"\n      },\n      \"title\": {\n        \"css\": \"#comic img\",\n        \"attr\": \"title\"\n      }\n    }\"\n"
26
+      Utils.unindent(a).should == "Events will have the fields you specified.  Your options look like:\n\n    {\n      \"url\": {\n\"css\": \"#comic img\",\n\"attr\": \"src\"\n      },\n      \"title\": {\n\"css\": \"#comic img\",\n\"attr\": \"title\"\n      }\n    }\""
27
+    end
28
+  end
29
+
4 30
   describe "#value_at" do
5 31
     it "returns the value at a JSON path" do
6 32
       Utils.value_at({ :foo => { :bar => :baz }}.to_json, "foo.bar").should == "baz"

+ 1 - 1
vendor/assets/javascripts/jquery.json-editor.js

@@ -212,7 +212,7 @@ JSONEditor.prototype.showFunctionButtons = function(insider) {
212 212
     }).text('Redo')).append($('<a id="toggle_view" href="#" style="padding-right: 10px;"></a>').click(function() {
213 213
       self.toggleBuilder();
214 214
       return false;
215
-    }).text('Toggle View').css("float", "right"));
215
+    }).text('Toggle View'));
216 216
     this.container.prepend(this.functionButtons);
217 217
     this.container.height(this.container.height() + this.functionButtons.height() + 5);
218 218
   }